-- SkillM
-- Create by weism
-- 技能模块

module("SkillM", package.seeall);

-- 所有的技能信息
local skills = {};

-- 技能树
local skillTree = {};

-- 所有的规则处理子模块
local rules = {};

-- 内部函数声明
local clearSkillInfo;
local doAfterCast;

-- 播放序列列表
local sequenceList = {};

-- 技能描述规则
local skillDescRule = {
  ["normal"] =  { "desc", "enter_ins_desc", "next_floor_desc", "special_spell_desc"},
  ["lictor"] =  { "desc", },
  ["lictor2"] = { "desc", "enter_ins_desc", },
  ["append"] =  { "append_desc", },
  ["share"] =   { "desc" },   -- 分享时显示
};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    local skill_CSV = get_skill_CSV();
    for i = 1, #skill_CSV do
        local skillId = skill_CSV[i].skill_id;
        skills[skillId] = skill_CSV[i];

        if skills[skillId].next and skills[skillId].next > 0 then
            skillTree[skills[skillId].next] = skillId;
        end
    end
    skill_CSV = {};
end

-- 模块初始化
function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH2("game/logic/module/skill");
    end
end

-- 返回技能配置表
function getSkillTable()
    return skills;
end

-- 检索技能配置
function query(skillId, path)
    if not skills[skillId] then
        return nil;
    end

    if path == nil or path == "" then
        return skills[skillId];
    end

    return skills[skillId][path];
end

-- 取得技能信息
function getRow(skillId)
    return skills[skillId];
end

-- 根据类型以及技能描述规则（skillDescRule）获取技能的描述
function getDescByType(skillId, skillType)
    -- 类型默认为 "normal"
    if not skillType or not skillDescRule[skillType] then
        skillType = "normal";
    end

    local desc = "";

    -- 遍历类型规则下需要的技能描述片段，将它们连接起来
    for _, descType in ipairs(skillDescRule[skillType]) do
        local str = query(skillId, descType);
        str = getSkillLocStr(str);

        if string.len(str) > 0 then
            if string.len(desc) > 0 then
                desc = desc .. "\n" .. str;
            else
                desc = str;
            end
        end
    end

    return desc;
end

-- 技能描述
function getSkillDesc(skillId, isLearn, skillType, extra)
    -- 如果配置了公式，直接使用公式返回的描述
    local desc = query(skillId, "desc");
    local prefix = "formula_";
    if type(desc) == "string" and string.startWith(desc, prefix) then
        local formulaName = string.sub(desc, string.len(prefix) + 1, string.len(desc));
        return FormulaM.invoke(formulaName, skillId);
    end

    -- 类型默认为 "normal"
    if not skillType then
        skillType = "normal";
    end

    if skillType == "lictor" then
        -- 灵魂链接要区分是否生效
        local dbase = query(skillId, "dbase") or {};
        skillType = dbase["lictor_desc_rule"] or "lictor";
    end

    if skillType == "append" then
        -- 只有满足各种条件才显示真义效果
        local key = FormulaM.invoke("CALC_SKILL_APPEND_PROP", skillId);
        if not key then
            skillType = "normal";
        end

        local prop = PropM.combine(ME.user, key, skillId);
        if prop[3] <= 0 then
            skillType = "normal";
        end
    end

    local desc = getDescByType(skillId, skillType);
    local descWhenLearn = query(skillId, "desc_when_learn");
    local prefix = "formula_";
    if type(descWhenLearn) == "string" and string.startWith(descWhenLearn, prefix) then
        local formulaName = string.sub(descWhenLearn, string.len(prefix) + 1);
        descWhenLearn = FormulaM.invoke(formulaName, skillId);
    else
        descWhenLearn = getSkillLocStr(descWhenLearn);
    end

    if isLearn == true then
        if desc == "" then
            desc = descWhenLearn;
        elseif descWhenLearn == "" then
            desc = desc;
        else
            desc = string.format("%s\n%s", descWhenLearn, desc);
        end
    end

    local row = SkillM.getRow(skillId);
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local f = queryRule(rule);

            local source = ME.user;
            if type(extra) == "table" and extra.source then
                source = extra.source;
            end

            desc = f.parseDesc(source, skillId, desc, f.parse(para), extra);
        end
    end

    return desc;
end

-- 获取技能的基础属性（攻击、魔力、生命、魔法）
function getSkillBaseProps(skillId)
    local row = SkillM.getRow(skillId);

    local parse = function(desc)
        local arr = string.explode(desc, ",");
        return { tonumber(arr[1]), arr[2], tonumber(arr[3]), tonumber(arr[4] or -1), };
    end;

    local ret = {};
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local prop = parse(para);

            if prop[1] == 200 and
              (prop[2] == "attack" or prop[2] == "magic" or
               prop[2] == "max_hp" or prop[2] == "max_mp") then
                table.insert(ret, prop);
            end
        end
    end

    return ret;
end

-----------------------
-- 获取技能的属性
-- @param skillId        技能id
-- @param fiterBaseProp  是否过滤基础属性
function getSkillProps(skillId, fiterBaseProp)
    local row = SkillM.getRow(skillId);

    local parse = function(desc)
        local arr = string.explode(desc, ",");
        return { tonumber(arr[1]), arr[2], tonumber(arr[3]), tonumber(arr[4] or -1), };
    end;

    -- 是否是基础属性
    local function isBaseProp(prop)
        if prop[1] == 200 and
          (prop[2] == "attack" or prop[2] == "magic" or
           prop[2] == "max_hp" or prop[2] == "max_mp") then
           return true;
        end
    end

    local ret = {};
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local prop = parse(para);

            if not fiterBaseProp or not isBaseProp(prop) then
                table.insert(ret, prop);
            end
        end
    end

    return ret;
end

-- 获取所有可以学习的技能
function filterToLearnSkills(filter)
    filter = filter or {};

    local excludes = {};
    for _, skillId in pairs(filter) do
        excludes[findBaseSkill(skillId)] = true;
    end

    local ret = {};

    -- 先遍历一遍所有技能，剔除需要过滤的技能
    for skillId, _ in pairs(skills) do
        -- 等级
        local level = skills[skillId].level or 0;

        if  level == 1 and skills[skillId].type == USER_PASSIVE and
            not excludes[findBaseSkill(skillId)] then
            table.insert(ret, skillId);
        end
    end

    -- 排序一遍
    table.sort(ret, function(a, b) return a < b; end);

    return ret;
end

-- 获取基类技能
function findBaseSkill(skillId)

    while skillTree[skillId] do
        skillId = skillTree[skillId];
    end

    return skillId;
end

-- 获取技能等级
function getSkillLevel(skillId)
    if SlimeSkillM.isSlimeSkill(skillId) then
        -- 冈布奥主动技能
        local id = SlimeSkillM.getSlimeActiveSkillId(skillId);
        return SlimeSkillM.getSkillLevel(id);
    end

    return query(skillId, "level");
end

-- 获取最高等级的技能
function findMaxSkill(skillId)
    local baseSkill = findBaseSkill(skillId);
    local next = query(baseSkill, "next");
    while next > 0 do
        skillId = next;
        next = query(skillId, "next");
    end

    return skillId;
end

-- 获取技能最高等级
function getSkillMaxLevel(skillId)
    local baseSkill = findBaseSkill(skillId);
    local next = query(baseSkill, "next");
    while next > 0 do
        skillId = next;
        next = query(skillId, "next");
    end

    return query(skillId, "level");
end

-- 获取技能，暂时全部遍历获取
function findSkill(skillId, level)
    while true do
        local info = skills[skillId];
        if not info then
            break;
        end

        -- 找到该技能
        if info.level == level then
            break;
        end

        -- 找下一个等级
        if info.level < level then
            if not info.next or info.next < 0 then
                break;
            end

            skillId = info.next;
        else
            -- 找上一个等级
            if not skillTree[skillId] then
                break;
            end

            skillId = skillTree[skillId];
        end
    end

    return skillId;
end

-- 检索技能信息
function query(skillId, path)
    local m = skills[skillId]

    if not m then
        return
    elseif (path == "" or path == nil) then
        return m;
    else
        return m[path];
    end
end

-- 获取子规则模块
function queryRule(rule)
    if rules[rule] == "TBL" then
        return LOAD_RUN("game/logic/module/skill/" .. rule);
    end

    return rules[rule]
end

-- 技能是否需要目标
function needTarget(skillId)
    local targetFlag = query(skillId, "target");

    -- 如果是ATTACKER、DEFENSERS的就不需要选目标了
    if targetFlag == ATTACKER or targetFlag == DEFENSERS then
        return false;
    end

    -- 其他的都需要选择目标
    return true;
end

-- 施展被动技
function castPassive(target, skillId)
    local row = SkillM.getRow(skillId);

    if not row then
        trace("SkillM", "无法施展被动技能，技能(%s)不存在", tostring(skillId));
        return;
    end

    -- 如果是主动技能
    if row["type"] == USER_SKILL then
        return;
    end

    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local f = queryRule(rule);
            f.apply(target, target, skillId, 0, f.parse(para));
        end
    end
end

-- 获取加成血量/魔量百分比信息
function getSkillHpMpUp(skillId)
    local row = SkillM.getRow(skillId);

    if not row then
        trace("SkillM", "无法获取被动技能，技能(%s)不存在", tostring(skillId));
        return;
    end

    local applyIds = { "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", };
    local attrib = {"max_hp", "max_mp"};
    local retInfos = {};
    for i = 1, #applyIds do
        local ruleDesc = query(skillId, applyIds[i]);
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local f = queryRule(rule);
            local paraArr = f.parse(para);

            if paraArr[1] == 1 and rule == "prop" and
               table.indexOf(attrib, paraArr[2]) ~= -1 then
                local info = { ["para"] = paraArr };
                table.insert(retInfos, info);
            end
        end
    end
    return retInfos;
end

-- 能否施放技能
function canCast(user, skillId, pos)
    if not skills[skillId] then
        return false;
    end

    -- 判断能否使用技能
    local ret = FormulaM.invoke("CAN_APPLY_SKILL", user, skillId, pos);
    if ret ~= true then
        return ret;
    end

    local target;
    if skills[skillId].target == ATTACKER then
        -- 给自己作用的技能
        target = user;
    else
        if not pos then
            return false;
        end

        -- 格子
        local grid = DungeonM.getGridByPos(pos);
        target = grid.monster;

        -- 没有怪物或者怪物已经死亡了
        if not target or target:isDead() then
            trace("SkillM", "怪物不存在或已死亡。");
            return false;
        end
    end

    -- 遍历所有作用规则
    local row = SkillM.getRow(skillId);
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local f = queryRule(rule);

            -- 不能释放，被拦截了
            if f.check then
                ret = f.check(skillId, para);
                if true ~= ret then
                    trace("SkillM", "经子规则判断，技能%d不能使用。", skillId);
                    return ret;
                end
            end
        end
    end

    return true;
end

-- 是否免疫技能
function immunitySkill(source, target, skillId)
    -- 无视免疫的
    if SlimeSkillM.defyImmunitySkill(skillId) then
        local prop = PropM.combine(target, "skill_immunity", 1);
        if prop[3] > 0 then
            return true;
        end

        prop = PropM.combine(target, "slime_skill_immunity", 1);
        if prop[3] > 0 then
            return true;
        end

        return false;
    end

    -- 没有该技能
    if not skills[skillId] then
        return false;
    end

    -- 不能被免疫的技能
    local dbase = skills[skillId]["dbase"] or {};
    if dbase["ignore_immunity"] == 1 then
        return false;
    end

    if source == target then
        -- 不能免疫对自己释放的技能
        return false;
    end

    -- 如果有免疫
    local props = PropM.fetchProps(target, "magic_immunity");
    local style = skills[skillId]["style"];

    if type(style) == "number" then
        for _, prop in ipairs(props) do
            if bit.band(tonumber(prop[2]), style) == style then
                -- 如果是免疫单系魔法，变形时不免疫
                if tonumber(prop[2]) > 0 then
                    if not FormulaM.invoke("MONSTER_IS_TRANSED", target) then
                        return true;
                    end
                else
                    return true;
                end
            end
        end
    end

    -- 如果有免疫阶位
    props = PropM.fetchProps(target, "order_bit_immunity");
    local rank = skills[skillId]["rank"];

    for _, prop in ipairs(props) do
        if tonumber(prop[2]) == rank then
            return true;
        end
    end

    -- 如果有免疫指定技能
    prop = PropM.combine(target, "magic_immunity2", skillId);
    if prop[3] > 0 then
        return true;
    end

    prop = PropM.combine(target, "skill_immunity", 1);
    if prop[3] > 0 then
        return true;
    end

    return false;
end

-- 获取目标
function getTargets(targetType, data)
    if targetType == TARGET_MAX_HP then
        -- 选择最大血量的
        local hp = -1;
        local target;
        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);
            local monster = grid.monster;

            if  grid:isOpened() and monster and not monster:isDead() then
                local monsterHp = monster:getHp();
                if monsterHp > hp then
                    hp = monster:getHp();
                    target = monster;
                end
            end
        end
        if target then
            return { target, };
        end

    elseif targetType == TARGET_MAX_ATTACK then
        -- 最高攻击的
        local attack = -1;
        local target;
        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);
            local monster = grid.monster;

            if  grid:isOpened() and monster and not monster:isDead() then
                local monsterAttack = monster:getAttack();
                if monsterAttack > attack then
                    attack = monsterAttack;
                    target = monster;
                end
            end
        end
        if target then
            return { target, };
        end

    elseif targetType == TARGET_TYPE_RANDOM then
        -- 随机目标
        local amount = data or 1;

        local arr = {};
        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);
            local monster = grid.monster;

            if  grid:isOpened() and monster and not monster:isDead() then
                table.insert(arr, monster);
            end
        end

        local len = #arr;
        if len <= 0 then
            return {};
        end

        amount = math.min(amount, len);

        local ret = {};
        for i = 1, amount do
            local rand = DungeonM.getRandSeed("random_target");
            local index = rand % len + 1;

            table.insert(ret, arr[index]);
            arr[index] = arr[len];
            len = len - 1;
        end

        return ret;

    elseif targetType == TARGET_TYPE_USER then
    -- 自己或全体敌人
        return { ME.user };

    else
        return {};
    end

    return {};
end

-- 施放技能
function cast(user, skillId, pos, extra)
    Profiler.funcBegin("cast");

    if not skills[skillId] then
        return false;
    end

    local target;
    if  not needTarget(skillId) then
        -- 不需要选目标的技能
        target = user;
    else
        -- 格子
        local grid = DungeonM.getGridByPos(pos);
        target = grid.monster;
    end

    local round = CombatM.getRound();

    -- 技能作用
    apply(user, target, skillId, round, extra);

    local fetchProps = PropM.fetchProps;
    local trigger = PropM.trigger;

    -- 触发其它技能或效果
    local props = fetchProps(user, "cure_magic_skill");
    for _, prop in ipairs(props) do
        trigger(user, prop[1], prop[2], skillId);
    end

    local props = fetchProps(user, "skill_upgrade");
    for _, prop in ipairs(props) do
        trigger(user, prop[1], prop[2], skillId);
    end

    -- 再来一发
    local props = fetchProps(user, "triple_spell");
    for _, prop in ipairs(props) do
        trigger(user, prop[1], prop[2], {["skill_id"] = skillId, ["target"] = target});
    end

    -- 触发时间停止
    props = fetchProps(user, "spell_to_stoptime");
    for _, prop in ipairs(props) do
        trigger(user, prop[1], prop[2], skills[skillId]["style"]);
    end

    if FormulaM.invoke("IS_DAMAGE_SKILL", skillId) then
        props = fetchProps(user, "damage_magic_summon");
        for _, prop in ipairs(props) do
            trigger(user, prop[1], prop[2], pos);
        end
        props = fetchProps(user, "damage_magic_summon2");
        for _, prop in ipairs(props) do
            trigger(user, prop[1], prop[2], pos);
        end
    end

    Profiler.funcEnd("cast");
end

-- 获取专属技的触发概率
function getApplyProbalility(propId)
    local skillId = SpecialSkillInfoM.query(propId, "skill_id");
    local row = SkillM.getRow(skillId);

    -- 遍历该技能的所有触发属性
    local applyProb;
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            -- 解析属性
            local _, __, ___, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");

            -- 如果该技能有配置该触发属性
            if string.find(para, propId) then
                -- 获取概率
                local arr = string.explode(para, ",");
                applyProb = arr[3];
            end
        end
    end

    return applyProb;
end

-- 主动技作用
function apply(source, target, skillId, round, extra)
    Profiler.funcBegin("Skill.apply");

    extra = extra or {};

    initSequence(source, target, skillId);
    getSequence(target):start(source, target, skillId);

    -- 1. 发挥作用
    local targets = {};
    local row = SkillM.getRow(skillId);
    for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
        local ruleDesc = row[field];
        if (ruleDesc and ruleDesc ~= "") then
            local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
            local f = queryRule(rule);

            -- 解析下参数规则
            local para = f.parse(para);

            -- 发挥作用
            targets = f.apply(source, target, skillId, round, para, extra);
        end
    end

    local pos = 0;

    if source.dbase:query("type") ~= OBJECT_TYPE_USER then
        pos = source:getPos();
    end
    -- 抛出事件
    Profiler.funcBegin("APPLY_SKILL");
    EventMgr.fire(event.APPLY_SKILL, {["skillId"] = skillId, ["source"] = source, ["target"] = target, ["pos"] = pos, });
    Profiler.funcEnd("APPLY_SKILL");

    Profiler.funcEnd("Skill.apply");

    -- 魔法不会被反击
    return targets;
end

-- 能否物理攻击
function canPhysic(source, target)
    if not DungeonM.checkDungeonAction() then
        -- 经检测，不允许执行地牢行为
        return false;
    end

    if not source or not target then
        trace("SkillM", "source或 target为nil");
        return false;
    end

    if target.dbase:query("flee") then
        -- 逃跑了
        return false;
    end

    return true;
end

-- 物理攻击
function physic(source, target, noSync)
    Profiler.funcBegin("physic");

    if not canPhysic(source, target) then
        -- 不满足攻击条件
        return false;
    end

    -- 当前回合
    if ENABLE_TRACE == 1 then
        trace("SkillM", "%s普通攻击%s。", source:getName(), target:getName());
        trace("SkillM", "%s当前血量：%d", source:getName(), source:getHp());
    end

    local countered = false;

    Profiler.funcBegin("physic1");

    -- 1. 怪物攻击
    -- 怪物攻击起始动作
    initSequence(target, source, 0);
    --sequenceList[source] = CastSequence.create(target, source, PHYSIC_ATTACK);
    local result = FormulaM.invoke("CAN_MONSTER_ATTACK_BACK", source, target);
    if true ~= result then
        --trace("SkillM", "%s不能反击，reason:%s。", target:getName(), result);
        -- 不能反击？记录一下
        DungeonLogM.addLog(string.format("%s不能反击，reason:%s", target:getName(), tostring(result)));
    elseif not FormulaM.invoke("HAPPEN_DODGE", target, source, DungeonM.getRandSeed("HAPPEN_DODGE")) then
        -- TODO: 闪避不及，需要计算具体的伤害
        Profiler.funcBegin("physic1-1");
        hit(target, source, PHYSIC_ATTACK);
        Profiler.funcEnd("physic1-1");

        -- 怪物反击了
        countered = true;
    else
        -- 闪避掉了
        Profiler.funcBegin("physic1-2");
        DungeonLogM.addLog(string.format("%s闪避,闪避率%d,命中率%d", source:getName(), source:getDodge(), target:getAccuracy()));
        -- trace("SkillM", "%s闪避。", source:getName());
        sequenceList[source]:dodge(target, source);
        EventMgr.fire(event.USER_DODGE);
        Profiler.funcEnd("physic1-2");
    end

    Profiler.funcEnd("physic1");
    Profiler.funcBegin("physic2");

    -- 2. 玩家攻击
    -- 玩家攻击起始动作
    initSequence(source, target, 0);
    sequenceList[source]:start(source, target, 0);
    --sequenceList[target] = CastSequence.create(source, target, PHYSIC_ATTACK);
    if not FormulaM.invoke("HAPPEN_DODGE", source, target, DungeonM.getRandSeed("HAPPEN_DODGE")) then
        -- TODO: 闪避不及，需要计算具体的伤害
        Profiler.funcBegin("physic1-2");
        hit(source, target, PHYSIC_ATTACK, { ["countered"] = countered, });
        Profiler.funcEnd("physic1-2");

        -- 触发aoe
        local props = PropM.fetchProps(source, "aoe");
        for _, prop in ipairs(props) do
            PropM.trigger(source, prop[1], prop[2], target);
        end

        -- 触发溅射同排怪
        local props = PropM.fetchProps(source, "splash_line");
        for _, prop in ipairs(props) do
            PropM.trigger(source, prop[1], prop[2], target);
        end
    else
        -- 闪避掉了
        DungeonLogM.addLog(string.format("%s闪避,闪避率%d,命中率%d", target:getName(), target:getDodge(), source:getAccuracy()));
        -- trace("SkillM", "%s闪避。", target:getName());
        sequenceList[target]:dodge(source, target);
    end

    Profiler.funcEnd("physic2");
    Profiler.funcBegin("physic3");

    -- 被玩家攻击之后
    if source.type == OBJECT_TYPE_USER then
        target.dbase:set("hit_by_user", CombatM.getRound());
    end

    if not noSync then
        -- 添加行为缓存
        DungeonM.addAction({ ["cmd"] = "physic_attack", ["pos"] = target:getPos(), });

        -- 一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, target:getPos());
    end

    Profiler.funcEnd("physic3");
    Profiler.funcBegin("physic4");

    -- 受攻击使自己技能无效
    local props = PropM.fetchProps(target, "invalid_skill");
    for _, prop in ipairs(props) do
        PropM.trigger(target, prop[1], prop[2], source);
    end
    Profiler.funcEnd("physic4");

    Profiler.funcEnd("physic");

    return true, true;
end

-- 对目标物理打击扣血
function hit(source, target, skillId, extra_data)
    Profiler.funcBegin("hit");

    -- 记录攻击者信息
    extra_data = extra_data or {};
    extra_data.assailantInfo =  {
        ["source"] = source,
        ["physic"]  = true,
    };

    -- 记录防守者信息
    source.dbase:setTemp("defenser_info", {
        ["target"] = target,
    });

    -- 记录下攻击回合
    source.dbase:set("hitRound", CombatM.getRound());

    initSequence(source, target, skillId);

    -- 玩家扣血、触发
    local attack = source:getAttack();

    local combine = PropM.combine;
    local trigger = PropM.trigger;
    local PropM_apply = PropM.apply;
    local fetchProps = PropM.fetchProps;

    -- 伤害为攻击的倍数(暂时只能为召唤兽)
    local prop = combine(source, "multiple_damage", 1);
    if prop[3] > 0 then
        attack = trigger(source, prop[1], prop[2], attack);
    end

    -- 被敌方百分比削弱
    prop = combine(target, "weak_enemy", "attack");
    attack = PropM_apply(prop, attack);

    -- 绝对值削弱
    prop = combine(target, "weak_enemy2", "attack");
    attack = PropM_apply(prop, attack);

    -- 基础伤害等于攻击
    local damage = attack;
    -- 伤害增幅；加成为正，减少为负
    local addon = 0;
    local props;
    local p;
    -- --------------- 计算source的prop影响下的总伤害 -------------------------------
    -- --------------- 先计算总的加成，最后再相加（百分比计算时不叠加） ---------------
    -- 概率额外伤害
    props = fetchProps(source, "probability_damage");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 古剑术（概率额外伤害）
    props = fetchProps(source, "fencing");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 醉拳（概率额外伤害）
    props = fetchProps(source, "drunken_master");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 额外魔法伤害
    prop = combine(source, "extrl_magic_damage", 1);
    if prop[3] > 0 then
         addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 概率反击伤害加成（无远程攻击怪物，远程攻击怪物也是hit故会有加成伤害）
    props = fetchProps(source, "add_counterattack");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 杀戮程式3倍伤害
    local opti = source.dbase:query("opti_point", 0);
    if opti >= 100 then
        addon = damage * 2;
        source.dbase:set("opti_point", opti - 100);

        -- 触发特技发动事件
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = 456 });
        EventMgr.fire(event.SOUL_COST, {["amount"] = 100, ["attrib"] = "opti_point" });
    end

    -- 目标被石化时造成额外伤害
    prop = combine(target, "petrifaction", 1);
    if PropM_apply(prop, 1) > 0 then
        addon = addon + trigger(target, prop[1], prop[2], damage);

        p = combine(source, "petrify_damage", 1);
        addon = addon + PropM_apply(p, damage);
    end

    -- 毁灭印记百分比伤害加成
    prop = combine(target, "destruction", 1);
    addon = addon + PropM_apply(prop, damage);

    -- 毁灭印记烙印或清除，必须在加成后面
    props = fetchProps(source, "destruction_mark");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 对被标记的目标造成额外伤害
    if target.dbase:query("track") then
        prop = combine(source, "monster_type_damage", "track");
        addon = addon + PropM_apply(prop, damage);
    end

    -- 对某种(如强盗)怪物额外伤害
    local baseClassId;
    if target.type == OBJECT_TYPE_MONSTER then
        baseClassId = target:query("base_class_id");
    end
    prop = combine(source, "target_damage", baseClassId);
    addon = addon + PropM_apply(prop, damage);

    -- 对某种(如强盗)怪物额外伤害（佐罗的面罩：怪物根据地图抽取）
    local dungeonId = DungeonM.getDungeonId();
    prop = combine(source, "target_damage1", 1);
    if baseClassId == FormulaM.invoke("CALC_ZOORO_MONSTER_DEMAND", dungeonId) then
        addon = addon + PropM_apply(prop, damage);
    end

    -- 对某类(大类，如亡灵)怪物额外伤害
    local style = target.style;
    prop = combine(source, "monster_type_damage", style);
    addon = addon + PropM_apply(prop, damage);

    -- 对远程怪物额外伤害
    if target.type == OBJECT_TYPE_MONSTER and target:isRemoteMonster() then
        prop = combine(source, "monster_type_damage", "remote");
        addon = addon + PropM_apply(prop, damage);
    end

    if FormulaM.invoke("IS_LIVING_MONSTER", target) then
        prop = combine(source, "monster_type_damage", "living");
        addon = addon + PropM_apply(prop, damage);
    end

    local monster_type;
    if target.type == OBJECT_TYPE_MONSTER then
        monster_type = target:query("monster_type");
    end
    if monster_type == MONSTER_TYPE_BOSS then
        -- 对BOSS额外伤害
        prop = combine(source, "monster_type_damage", "boss");
        addon = addon + PropM_apply(prop, damage);

        -- 专属：斩首行动
        prop = combine(source, "decapitate", "boss");
        addon = addon + PropM_apply(prop, damage);
    else
        -- 对非BOSS额外伤害
        prop = combine(source, "monster_type_damage", "not_boss");
        addon = addon + PropM_apply(prop, damage);
    end

    -- 概率额外固定伤害
    props = fetchProps(source, "probability_damage2");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 目标被撕裂带伤口时造成额外伤害
    prop = combine(target, "tear", 1);
    if PropM_apply(prop, 1) > 0 then
        prop = combine(source, "tear_damage", 1);
        if prop[3] > 0 then
            addon = addon + trigger(source, "tear_damage", 1);
        end
    end

    -- 召唤兽通灵造成额外伤害
    props = fetchProps(source, "summon_telepathize");
    for _, prop in ipairs(props) do
        addon = addon + trigger(source, prop[1], prop[2], damage);
    end

    -- 比你漂亮的敌人伤害加成
    prop = combine(source, "kill_beauty", 1);
    addon = addon + PropM_apply(prop, damage);

    damage = damage + addon;
    -- --------------- 计算target的prop影响下的总伤害 ----------------------
    -- --------------- 先百分比后绝对值，百分比计算时直接叠加 ---------------
    addon = 0;

    -- 物理抗性
    prop = combine(target, "resist", "attack");
    -- 忽视抗性
    p = combine(source, "ignore_resist", "attack");
    prop[3] = PropM_apply(p, prop[3]);
    -- 开板降低物抗
    props = fetchProps(target, "open_grid_reduce");
    for _, p in ipairs(props) do
        if p[2] == "attack_resist" then
            prop[3] = trigger(target, p[1], p[2], prop[3]);
        end
    end

    -- 阵亡同伴数量降低物抗
    props = fetchProps(target, "weak");
    for _, p in ipairs(props) do
        if p[2] == "attack_resist" then
            prop[3] = trigger(target, p[1], p[2], prop[3]);
        end
    end

    -- 物抗对伤害影响
    damage = PropM_apply(prop, damage);

    -- 减少%伤害
    props = fetchProps(target, "less_damage_hit1");
    for _, prop in ipairs(props) do
        damage = trigger(target, prop[1], prop[2], damage);
    end

    -- 魔力熔炉专属技消耗魔法抵消伤害
    props = fetchProps(target, "hit_offset");
    for _, prop in ipairs(props) do
        if trigger(target, prop[1], prop[2]) then
            damage = PropM_apply(prop, damage);
        end
    end

    -- 最终伤害，不能低于0
    damage = damage + addon;
    damage = math.max(damage, 0);

    -- 目标受创
    Profiler.funcBegin("hit:receiveDamage");
    CombatM.receiveDamage(source, target, damage, skillId, extra_data);
    Profiler.funcEnd("hit:receiveDamage");

    -- 增加命中动作
    if damage > 0 and not target.noHitEffect then
        sequenceList[target]:hit(source, target, skillId);
    else
        -- 只针对本次起效
        target.noHitEffect = false;
    end

    -- 概率触发技能
    local props = fetchProps(source, "attack_skill");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 亡灵契约：概率触发技能
    local props = fetchProps(source, "undead_contract");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 雷神之锤：概率触发技能
    local props = fetchProps(source, "thor");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 召唤兽触发技能
    props = fetchProps(source, "summon_attack_skill");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率回血
    local props = fetchProps(source, "attack_cure");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2]);
    end

    -- 概率燃烧
    local props = fetchProps(source, "attack_burn");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率中毒
    local props = fetchProps(source, "attack_poison");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率刺穿
    local props = fetchProps(source, "attack_pierce");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率诅咒
    local props = fetchProps(source, "attack_curse");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率震撼（使怪物不反击）
    local props = fetchProps(source, "attack_frighten");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率切割（使怪物不反击）
    local props = fetchProps(source, "hp_to_attack_frighten");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率腐蚀（使怪物降攻并持续掉血）
    local props = fetchProps(source, "attack_corriosion");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率惊吓（使怪物降血）
    local props = fetchProps(source, "attack_startle");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率撕裂
    local props = fetchProps(source, "attack_tear");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率附加通用状态
    local props = fetchProps(source, "attack_status");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 概率附加状态
    local props = fetchProps(source, "crime_crackers");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 清除状态
    props = fetchProps(source, "attack_clear_status");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2]);
    end

    -- 杀戮程式优化点
    local props = fetchProps(source, "optimization");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 攻击时增加专属属性点（守护者怒气）
    local props = fetchProps(source, "attack_gain_attrib");
    for _, prop in ipairs(props) do
        trigger(source, prop[1], prop[2], target);
    end

    -- 吸血类，只对有生命的敌人起效
    local hp;
    if FormulaM.invoke("IS_LIVING_TARGET", target) and damage > 0 then
        -- 直接吸血
        trigger(source, "absorb", 1, damage);
        -- 概率吸血
        local props = fetchProps(source, "probability_absorb");
        for _, prop in ipairs(props) do
            trigger(source, prop[1], prop[2], damage);
        end
    end

    -- 被光环辅助,在场时，全场同伴攻击附加吸血效果
    prop = PropM.combine(source, "aid_absorb", 1);
    if prop[3] > 0 then
        hp = PropM.apply(prop, damage);
        if source.type ~= OBJECT_TYPE_USER then
            -- 怪物打玩家直接吸血
            CombatM.receiveCure(source, source, hp);
        elseif FormulaM.invoke("IS_LIVING_TARGET", target) then
            -- 玩家打怪物需要判断怪物有生命
            BonusM.doBonus({ 2, "hp", hp, }, "aid_absorb");
            EventMgr.fire(event.HP_VAMPIRE, hp);
        end
    end

    -- 如果是召唤兽，尝试吸血并转移给玩家
    if source.type == OBJECT_TYPE_SUMMON then
        trigger(source, "absorb", 2, damage);
    end

    -- 概率反击吸血
    trigger(source, "add_counterattack_absorb", 1, damage);

    -- 根据原始攻击反射伤害
    prop = combine(target, "reflex_damage", 1);
    local reflexDamage = PropM_apply(prop, attack);
    prop = combine(source, "reflex_resist", 1);
    reflexDamage = PropM_apply(prop, reflexDamage);
    if PHYSIC_ATTACK == skillId and reflexDamage > 0 and not FormulaM.invoke("MONSTER_IS_STOPED", target) then
        -- 增加反伤动作
        getSequence(target):reflex(source, target);

        -- 增加命中动作
        initSequence(target, source, skillId);
        sequenceList[source]:hit(target, source, PHYSIC_ATTACK);

        -- 反射伤害
        CombatM.receiveDamage(target, source, reflexDamage, PHYSIC_ATTACK);
    end

    -- 被攻击者反击触发
    CombatM.woundedTrigger(target, source, damage);

    Profiler.funcEnd("hit");
end

-- 清空防守者和攻击者的受创数据
clearSkillInfo = function(source, target)
    for _, f in pairs(_G.templet:getFriends(source)) do
        f.dbase:setTemp("damage_hp", 0);
    end
    for _, f in pairs(_G.templet:getFriends(target)) do
        f.dbase:setTemp("damage_hp", 0);
    end
end

-- 技能施展后的处理
doAfterCast = function(source, target)
    -- 不是攻击的技能，暂时不需要做其他处理
    if source.dbase:queryTemp("damage_hp", 0) == 0 then return end

    -- 攻击者尝试追击
    local damage = FormulaM.invoke("PURSUIT", source, target);
    if (damage > 0) then
        _G.templet:receiveDamage(source, target, damage, PURSUIT);
    end

    -- 攻击者尝试回血
    local hp = FormulaM.invoke("CURE_AFTER_ATTACK", source, target);
    if (hp > 0) then
        _G.templet:receiveCure(source, source, hp);
    end

    -- 防守方尝试反击
    damage = FormulaM.invoke("HITBACK", target, source);
    if (damage > 0) then
        _G.templet:receiveDamage(target, source, damage, HITBACK);
    end

    -- 防守方尝试回血
    hp = FormulaM.invoke("CURE_AFTER_DAMAGE", target, source);
    if (hp > 0) then
        _G.templet:receiveCure(target, target, hp);
    end
end

-- 判断能否升级技能
function canUpgradeSkill(who, skillId)
    -- 该技能不能升级
    local nextSkill = query(skillId, "next");
    if not nextSkill or nextSkill <= 0 then
        return false
    end

    local own = false

    if who.type == OBJECT_TYPE_PET then
        -- 宠物
        if who.skillId == skillId then
            -- 主动技能
            own = true
        elseif who.passiveSkill == skillId then
            -- 被动技能
            own = true
        end

        if not own then
            -- 没有该技能
            return false
        end
    elseif who.type == OBJECT_TYPE_USER then
        -- 玩家
        for _, id in pairs(who.dbase:query("skills", {})) do
            if id == skillId then
                -- 找到该技能
                own = true
                break
            end
        end

        if not own then
            -- 没有该技能
            return false
        end
    else
        -- 其他类型的对象不能升级技能
        return false
    end

    -- 升级消耗消耗
    local ok = true
    local cost = FormulaM.invoke("CALC_UPGRADE_SKILL_COST", who, skillId);
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            ok = ItemM.getAmount(ME.user, arr[2]) >= arr[3]
        elseif arr[1] == 2 then
            -- 属性消耗
            ok = ME.user.dbase:query(arr[2], 0) >= arr[3]
        else
            -- 暂不支持其他类型的消耗
            return false
        end

        -- 消耗不足
        if not ok then
            return false
        end
    end

    return ok
end

-- 升级玩家技能
function upgradeUserSkill(skillId)
    -- 经判断不能升级
    if not canUpgradeSkill(ME.user, skillId) then
        return false
    end

    -- 该技能不能升级？
    local nextSkill = query(skillId, "next")
    if nextSkill <= 0 then
        return false
    end

    -- 扣除消耗
    local cost = FormulaM.invoke("CALC_UPGRADE_SKILL_COST", ME.user, skillId)
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 消耗物品
            ItemM.costAmount(ME.user, arr[2], arr[3])
        elseif arr[1] == 2 then
            -- 消耗属性
            local amount = ME.user.dbase:query(arr[2], 0) - arr[3]
            ME.user.dbase:set(arr[2], amount)
        end
    end

    -- 技能
    local userSkills = ME.user.skills

    -- 覆盖掉旧技能
    userSkills[table.indexOf(userSkills, skillId)] = nextSkill

    -- 玩家技能升级事件
    EventMgr.fire(event.UPGRADE_USER_SKILL, skillId, nextSkill);

    -- 刷新附加属性
    PropM.refresh(ME.user);

    return true
end

-- 宠物技能升级
function upgradePetSkill(skillId, classId)
    local pet = ME.user.pets[classId]

    -- 玩家没有该宠物
    if not pet then
        return false
    end

    -- 经判断不能升级
    if not canUpgradeSkill(pet, skillId) then
        return false
    end

    -- 扣除消耗
    local cost = FormulaM.invoke("CALC_UPGRADE_SKILL_COST", pet, skillId)
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 消耗物品
            ItemM.costAmount(ME.user, arr[2], arr[3])
        elseif arr[1] == 2 then
            -- 消耗属性
            local amount = ME.user.dbase:query(arr[2], 0) - arr[3]
            ME.user.dbase:set(arr[2], amount)
        end
    end

    -- 下一级的技能
    local nextSkill = query(skillId, "next")
    if nextSkill <= 0 then
        return false
    end

    -- 升级
    if pet.skillId == skillId then
        -- 主动技能
        pet.skillId = nextSkill
    elseif pet.passiveSkill == skillId then
        -- 被动技能
        pet.passiveSkill = nextSkill
    else
        return false
    end

    -- 宠物技能升级事件
    EventMgr.fire(event.UPGRADE_PET_SKILL, classId, skillId, nextSkill);

    -- 刷新附加属性
    PropM.refresh(ME.user);

    return true
end

--获取可学的玩家技能列表
function getUserSkillsToLearn()
    local list = {};
    for skillId, _ in pairs(skills) do
        if query(skillId, "type") == USER_PASSIVE and query(skillId, "level") == 1 then
            table.insert(list, skillId);
        end
    end
    return list;
end

-- 初始化一个动作序列
function initSequence(source, target, skillId)
    if not sequenceList[target] then
        sequenceList[target] = CastSequence.create(source, target, skillId);
    end
end

-- 获取动作序列
function getSequence(target)
    return sequenceList[target];
end

-- 获取动作序列列表
function getSequenceList()
    return sequenceList;
end

-- 清空动作序列
function clearSequenceList()
    sequenceList = {};
end

-- 刷新技能带来的属性加成
function refreshSkillProp(skillId)
    local prop = ME.user.dbase:queryTemp("prop")["skill"];
    local row = SkillM.getRow(skillId);

    for _, skillProp in pairs(prop) do
        for _, field in ipairs({ "apply1", "apply2", "apply3", "apply4", "apply5", "apply6", }) do
            local ruleDesc = row[field];
            if (ruleDesc and ruleDesc ~= "") then
                local _, __, rule, para = string.find(ruleDesc, "([^%(]+)%((.*)%)$");
                local f = queryRule(rule);

                -- 解析下参数规则
                local para = f.parse(para);

                if skillProp[1] == para[1] and skillProp[2] == para[2] then
                    -- 需要扣除原技能带来的效果
                    skillProp[3] = skillProp[3] - para[3];
                end
            end
        end
    end
end

-- 获取地牢中的特殊技能
function getDungeonSpecialSkills()
    local ret = {};

    -- 1.石柱技能
    local stoneSkills = MagicStoneM.getStoneSkills(USER_SKILL);

    ret = table.append(ret, stoneSkills);

    -- 2. 元素球魔法
    local elementMagics = ElementMagicM.getAllMagics(ME.user);
    ret = table.append(ret, table.keys(elementMagics));

    -- 3. 冈布奥主动技能
    local slimeSkills = SlimeSkillM.getAllSkills();
    ret = table.append(ret, slimeSkills);

    -- 4.龙语魔法
    local dragonMagics = SlimeSkillM.getAllDragonMagic();
    ret = table.append(ret, dragonMagics);

    return ret;
end

-- 触发了一个技能，先缓存起来
function skillTriggerCache(who, skillId, target)
    local queue = who.dbase:queryTemp("skillTriggerQueue", {});
    local round = CombatM.getRound();

    if "forbid" == queue[round] then
        return;
    end

    queue[round] = queue[round] or {};

    target = target or 0;
    queue[round][skillId] = target;

    who.dbase:setTemp("skillTriggerQueue", queue);

    -- 触发
    -- skillTrigger(who);
end

-- 选择目标
function fidntriggerTarget(who, skillId)
    -- 目标
    local target;
    if not needTarget(skillId) then
        target = who;
    else
        for pos = 1, DUNGEON_WIDTH * DUNGEON_HEIGHT do
            local grid = DungeonM.getGridByPos(pos);

            -- 是未死亡的怪物
            if grid:isOpened() and
                grid:isMonster() and
                not grid.monster:isDead() then
                target = grid.monster;
                break;
            end
        end
    end

    return target;
end

-- 触发技能
function skillTrigger(who)
    local queue = who.dbase:queryTemp("skillTriggerQueue", {});
    local round = CombatM.getRound();

    if not queue[round] then
        return;
    end

    local map = queue[round];
    local keys = table.keys(map);

    -- 禁止再触发
    queue[round] = "forbid";

    -- 先排序一下，保证触发顺序和服务器一致
    table.sort(keys);

    for index = 1, #keys do
        local skillId = keys[index];

        local target = map[skillId];
        -- 若没有未死亡的指定目标，则在场上找一个
        if target == 0 or target:isDead()then
            target = fidntriggerTarget(who, skillId);
        end

        if target then
            initSequence(who, target, skillId);
            getSequence(target):delay2(who, target, 0.1);
            SkillM.apply(who, target, skillId, round);
        end
    end

    queue[round] = nil;
end


-- 获取技能的消耗类型
-- @param skillId    技能id
function getSkillCostType(skillId)
    local costType = skills[skillId]["cost_type"];

    if costType == "" then
        cclog("技能 %d 未配置cost_type", skillId);
        return "spell";
    else
        return costType;
    end
end

-- 计算魔法效果加成
function calcMagicEffectAddon(who, skillId, triggerEvent)
    -- 加成率,千分比
    local addonRate = 0;

    local combine = PropM.combine;
    local PropM_apply = PropM.apply;

    -- 1.指定技能效果加成
    local prop = combine(who, "magic_effect", skillId);
    addonRate = addonRate + PropM_apply(prop, 1);

    -- 2.所有系技能威力加成
    prop = combine(who, "magic_effect_style", "all");
    addonRate = addonRate + PropM_apply(prop, 1);
    if prop[3] > 0 and triggerEvent then
        -- 触发特技发动事件
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = prop[1], key = prop[2], });
    end

    -- 3.某系技能威力加成
    prop = combine(who, "magic_effect_style", SkillM.query(skillId, "style"));
    addonRate = addonRate + PropM_apply(prop, 1);
    if prop[3] > 0 and triggerEvent then
        -- 触发特技发动事件
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = prop[1], key = prop[2], });
    end

    --   某系技能威力加成（光与暗）
    prop = combine(who, "light_and_dark", SkillM.query(skillId, "style"));
    addonRate = addonRate + PropM_apply(prop, 1);
    if prop[3] > 0 and triggerEvent then
        -- 触发特技发动事件
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = prop[1] });
    end

    -- 4.按类型的技能威力加成
    local effectStyle = SkillM.query(skillId, "effect_style");
    if effectStyle == ACTIVE_SKILL_DAMAGE then
        -- 伤害类魔法威力加成
        prop = combine(who, "magic_effect_style", "damage");
        addonRate = addonRate + PropM_apply(prop, 1);

        -- 低阶伤害魔法威力加成
        if SkillM.query(skillId,"rank") < 3 then
            prop = combine(who, "powerful_spell", 1);
            addonRate = addonRate + PropM_apply(prop, 1);
        end

        -- 中阶伤害魔法威力加成
        if SkillM.query(skillId,"rank") == 3 then
            prop = combine(who, "powerful_spell3", 1);
            addonRate = addonRate + PropM_apply(prop, 1);
        end

        -- 高阶伤害魔法威力加成
        if SkillM.query(skillId,"rank") > 3 then
            prop = combine(who, "powerful_spell2", 1);
            addonRate = addonRate + PropM_apply(prop, 1);
        end
    elseif effectStyle == ACTIVE_SKILL_CURE then
        -- 恢复类魔法效果加成
        prop = combine(who, "magic_effect_style", "cure");
        addonRate = addonRate + PropM_apply(prop, 1);
        if prop[3] > 0 and triggerEvent then
            -- 触发特技发动事件
            EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = prop[1], key = prop[2], });
        end
    else
        -- 辅助魔法威力加成
        prop = combine(who, "magic_effect_style", "assist");
        addonRate = addonRate + PropM_apply(prop, 1);
    end

    return addonRate;
end
